package fgin

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel/fmeter"
	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel/ftracer"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"github.com/prometheus/client_golang/prometheus"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

func addMiddleWare(r *gin.Engine) {
	middleWareLs := []gin.HandlerFunc{
		firstMiddware(),
		otelMeterMiddware(),    // otel 开启监控
		otelTracerMiddleWare(), // otel 开启链路追踪
		timeoutMiddware(),
		recoverMiddleWare(),
		fginRateLimiterMiddleware(),
		fginCircuitBreakerMiddleware(),
	}
	if (fginConfig.Log != Log{}) {
		// 如果日志配置不为空，则自动增加入gin中间件
		middleWareLs = append(middleWareLs, logMiddleware())
	}
	if (fginConfig.Cors != Cors{}) {
		// 配置解决跨域
		middleWareLs = append(middleWareLs, corsMiddleWare())
	}
	if err := checkAuthConfig(); err == nil {
		// 配置了鉴权则加入中间件
		middleWareLs = append(middleWareLs, authMiddleware())
	}
	if fginConfig.Jaeger.AddressHTTP != "" || fginConfig.Jaeger.Address != "" {
		// 配置了jaeger则开启链路追踪
		if fginConfig.ServiceName == "" {
			logSuger.Error("开启jaeger链路追踪,service_name必须配置")
			panic(errors.New("开启jaeger链路追踪,service_name必须配置"))
		}
		middleWareLs = append(middleWareLs, jaegerMiddleWare())
	}
	if (fginConfig.Prometheus != Prometheus{}) {
		// 配置了prometheus则开启链路追踪
		if fginConfig.ServiceName == "" {
			logSuger.Error("开启prometheus监控,service_name必须配置")
			panic(errors.New("开启prometheus监控,service_name必须配置"))
		}
		middleWareLs = append(middleWareLs, prometheusMiddleWare())
	}
	// 将响应体暴露出去
	middleWareLs = append(middleWareLs, fginResponseMiddleware())
	r.Use(middleWareLs...)
}

// 首个中间件，用于预先处理的上下文
func firstMiddware() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		ch := make(chan int, 1)
		ch <- 1
		ctx.Set(RETURNLOCK, ch)
		ctx.Set(REQUESTSTARTTIME, time.Now())
		ctx.Next()
	}
}

// otel meter 监控中间件
func otelMeterMiddware() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		if fginConfig.Otel.PrometheusAddress == "" {
			// 未配置自动return
			ctx.Next()
			return
		}
		meter := fmeter.NewMeter(fginConfig.ServiceName)
		ctx.Next()
		defer func() {
			meter.DefaultMetric(ctx, ctx.GetTime(REQUESTSTARTTIME), "http", ctx.Request.Method, ctx.Request.URL.Path, fmt.Sprintf("%v", ctx.Writer.Status()))
		}()
	}
}

// 超时控制中间件
func timeoutMiddware() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		if fginConfig.Timeout <= 0 {
			ctx.Next()
			return
		}
		newContext, cancel := context.WithTimeout(ctx.Request.Context(), time.Duration(fginConfig.Timeout)*time.Millisecond)
		defer cancel()
		ctx.Request = ctx.Request.WithContext(newContext)
		finshCh := make(chan int, 1)
		go func() {
			ctx.Next()
			finshCh <- 1
		}()
		select {
		case <-finshCh:
		case <-time.After(time.Duration(fginConfig.Timeout) * time.Millisecond):
			ctx.Abort()
			// 耗时
			consTime := getConsTime(ctx)
			logSuger.Error(fmt.Sprintf("%s, timeout: %d ms, cons: %v", ctx.Request.URL.Path, fginConfig.Timeout, consTime), ctx)
			ReturnJson(ctx, 408, "", fmt.Errorf("request timeout"))
		}
	}
}

// 日志中间件
func logMiddleware() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		// 记录请求日志
		bodyParamsB, _ := io.ReadAll(ctx.Request.Body)
		// body只能读取一次，所以要重新写回
		ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyParamsB))
		ctx.Next()
		// 耗时
		consTime := getConsTime(ctx)
		// 处理为紧凑的json
		paramsMap := map[string]any{}
		_ = json.Unmarshal(bodyParamsB, &paramsMap)
		bodyParams, _ := json.Marshal(paramsMap)
		logInfo := fmt.Sprintf("ip:%s, code: %v, cons:%v, method:%s, uri:%s, form:%v, body:%s", ctx.ClientIP(), ctx.Writer.Status(), consTime, ctx.Request.Method, ctx.Request.URL, ctx.Request.Form, bodyParams)
		defer func() {
			if ctx.Request.URL.Path == "/ping" {
				// 如果是探针接口，不记录入日志
			} else {
				logSuger.Info(logInfo, ctx)
			}
		}()
	}
}

// jwt鉴权中间件
func authMiddleware() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		if ctx.Request.URL.Path == "/ping" {
			// 如果是探针接口，不记录入日志
			ctx.Next()
			return
		}
		for _, v := range fginConfig.Auth.DisableLs {
			// 指定路由，不进行鉴权
			// 指定完整路由也不进行鉴权
			subRouter := fmt.Sprintf("/%v/", v)
			if strings.Index(ctx.Request.URL.Path, subRouter) == 0 || strings.Index(ctx.Request.URL.Path, v) == 0 || strings.Index(ctx.Request.URL.Path, "/"+v) == 0 {
				ctx.Next()
				return
			}
		}
		// 优先header获取鉴权参数
		token := ctx.GetHeader(fginConfig.Auth.HeaderKey)
		// 如果配置了cookie， header取到token为空， 则从cookie取值
		if token == "" && fginConfig.Auth.CookieKey != "" {
			tokenC, err := ctx.Cookie(fginConfig.Auth.CookieKey)
			if err != nil {
				token = ""
			}
			token = tokenC
		}
		if token != "" {
			claims, err := ParsearseToken(token)
			if err != nil {
				ReturnJson(ctx, 401, nil, err)
				ctx.Abort()
				return
			}
			for k, v := range claims.AuthData {
				ctx.Set(k, v)
			}
		} else {
			ReturnJson(ctx, 401, nil, errors.New("暂未授权"))
			ctx.Abort()
			return
		}
		ctx.Next()
	}
}

// otel链路追踪
func otelTracerMiddleWare() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		if fginConfig.Otel.TracerHttp == "" {
			// 未配置直接return
			ctx.Next()
			return
		}
		tracer := ftracer.NewTracer(trace.SpanKindClient, ctx.Request.URL.Path)
		spanCtx, span := tracer.Start(ctx, ctx.Request.URL.Path)
		// 传递span到上下文
		ctx.Request = ctx.Request.WithContext(spanCtx)
		ctx.Next()
		defer func() {
			var responseBody string
			var err error
			if ctx.Writer.Status() < 200 || ctx.Writer.Status() >= 300 {
				// 从上下文中获取 CustomResponseWriter
				if customWriter, ok := ctx.Get(FGINRESPONSE); ok {
					writer := customWriter.(*FginResponseWriter)
					// 获取响应状态码和响应体内容
					responseBody = writer.body.String()
					err = errors.New(responseBody)
				}
			}
			span.SetAttributes(
				attribute.String("method", ctx.Request.Method),
				attribute.String("status", fmt.Sprintf("%v", ctx.Writer.Status())),
			)
			tracer.End(span, err)
		}()
	}
}

// jaeger链路追踪
func jaegerMiddleWare() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		// 设置请求的上下文
		var tracer opentracing.Tracer
		if opentracing.IsGlobalTracerRegistered() {
			tracer = opentracing.GlobalTracer()
		} else {
			logSuger.Error("http jaeger GlobalTracer is undefine", ctx)
			ctx.Next()
		}
		var span opentracing.Span
		header := ctx.Request.Header
		spanCtx, err := opentracing.GlobalTracer().Extract(
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(header),
		)
		if err == nil {
			span = opentracing.StartSpan(ctx.Request.URL.Path, opentracing.ChildOf(spanCtx))
		} else {
			span = tracer.StartSpan(ctx.Request.URL.Path)
		}
		// 将 span 注入到请求的上下文中
		ctx.Request = ctx.Request.WithContext(opentracing.ContextWithSpan(ctx.Request.Context(), span))
		// 继续处理请求
		ctx.Next()
		defer func() {
			var responseBody string
			if ctx.Writer.Status() < 200 || ctx.Writer.Status() >= 300 {
				span.SetTag("error", true)
				// 从上下文中获取 CustomResponseWriter
				if customWriter, ok := ctx.Get(FGINRESPONSE); ok {
					writer := customWriter.(*FginResponseWriter)
					// 获取响应状态码和响应体内容
					responseBody = writer.body.String()
				}
			}
			span.SetTag("method", ctx.Request.Method)
			span.SetTag("status", ctx.Writer.Status())
			span.LogKV("body", responseBody)
			span.Finish()
		}()
	}
}

// 异常抓取中间件
func recoverMiddleWare() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// 耗时
				consTime := getConsTime(ctx)
				errStr := fmt.Sprintf("cons: %v, err: %v", consTime, err)
				if (fginConfig.Log == Log{} || logSuger == nil) {
					fmt.Println(errStr)
				} else {
					logSuger.Error(errStr, ctx)
				}
				ReturnJson(ctx, 500, nil, errors.New("service error"))
				// 可以增加一个邮件报警,机器人报警
			}
		}()
		ctx.Next()
	}
}

// 跨域中间件
func corsMiddleWare() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		method := ctx.Request.Method
		origin := ctx.Request.Header.Get("Origin")
		if origin != "" {
			switch {
			case fginConfig.Cors.UseDefault:
				ctx.Header("Access-Control-Allow-Origin", "*")                                                                                                                          // 可将将 * 替换为指定的域名
				ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")                                                                                   // 允许的请求
				ctx.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")                                                             // 允许客户端携带的头
				ctx.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") // 响应暴露头
				ctx.Header("Access-Control-Allow-Credentials", "true")                                                                                                                  //允许客户端携带信息头，比如cookie
				ctx.Header("Access-Control-Max-Age", "86400")
			default:
				// 默认使用自定义配置
				originMap := make(map[string]string)
				originStr := strings.ReplaceAll(fginConfig.Cors.AllowOrigins, " ", "")
				for _, v := range strings.Split(originStr, ",") {
					originMap[v] = v
				}
				if v, ok := originMap["*"]; ok {
					ctx.Header("Access-Control-Allow-Origin", v) // 如果配置了*，则优先使用
				}
				if v, ok := originMap[origin]; ok {
					ctx.Header("Access-Control-Allow-Origin", v) // 可将将 * 替换为指定的域名
				}
				ctx.Header("Access-Control-Allow-Methods", fginConfig.Cors.AllowMethods)                            // 允许的请求
				ctx.Header("Access-Control-Allow-Headers", fginConfig.Cors.AllowHeaders)                            // 允许客户端携带的头
				ctx.Header("Access-Control-Expose-Headers", fginConfig.Cors.ExposeHeaders)                          // 响应暴露头
				ctx.Header("Access-Control-Allow-Credentials", fmt.Sprintf("%v", fginConfig.Cors.AllowCredentials)) //允许客户端携带信息头，比如cookie
				ctx.Header("Access-Control-Max-Age", fmt.Sprintf("%v", fginConfig.Cors.MaxAge))
			}
		}
		if method == "OPTIONS" {
			ctx.AbortWithStatus(http.StatusNoContent)
		}
		ctx.Next()
	}
}

// prometheus 中间件
func prometheusMiddleWare() func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		defer func() {
			// 计数
			requestCount.WithLabelValues(ctx.Request.Method, ctx.Request.URL.Path, fmt.Sprintf("%v", ctx.Writer.Status())).Inc()
			// 耗时
			obs := requestHistogram.WithLabelValues(ctx.Request.Method, ctx.Request.URL.Path, fmt.Sprintf("%v", ctx.Writer.Status()))
			timer := prometheus.NewTimer(obs)
			timer.ObserveDuration()
		}()
		ctx.Next()
	}
}

// 定义一个自定义的 ResponseWriter，以便我们可以捕获响应数据
type FginResponseWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w *FginResponseWriter) Write(b []byte) (int, error) {
	// 捕获响应数据到 buffer 中
	w.body.Write(b)
	// 调用原始的 Write 方法
	return w.ResponseWriter.Write(b)
}

// 中间件函数，用于创建 CustomResponseWriter,将响应体记录到上下文
func fginResponseMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 创建一个自定义的 ResponseWriter
		customWriter := &FginResponseWriter{
			ResponseWriter: ctx.Writer,
			body:           bytes.NewBufferString(""),
		}
		// 将自定义 Writer 替换默认的 ResponseWriter
		ctx.Writer = customWriter
		// 将自定义 Writer 存储到上下文中，以便后续中间件访问
		ctx.Set(FGINRESPONSE, customWriter)
		// 继续处理请求
		ctx.Next()
	}
}

// 限流器中间件
func fginRateLimiterMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if err := rateLimiter.Allow(); err != nil {
			logSuger.Info(fginConfig.ServiceName+":"+ctx.Request.URL.Path+rateErrMsg, ctx)
			ReturnJson(ctx, rateCode, "", errors.New(fginConfig.ServiceName+":"+ctx.Request.URL.Path+rateErrMsg))
			ctx.Abort()
			return
		}
		ctx.Next()
	}
}

// 熔断中间件
func fginCircuitBreakerMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if err := circuitBreaker.Allow(); err != nil {
			logSuger.Info(fginConfig.ServiceName+":"+ctx.Request.URL.Path+circuitErrMsg, ctx)
			ReturnJson(ctx, circuitCode, "", errors.New(fginConfig.ServiceName+":"+ctx.Request.URL.Path+circuitErrMsg))
			ctx.Abort()
			return
		}
		ctx.Next()
		// 500之间状态码，定义为可熔断
		if ctx.Writer.Status() < 600 || ctx.Writer.Status() >= 500 {
			// 标记失败
			circuitBreaker.MarkFailed()
		} else {
			// 标记成功
			circuitBreaker.MarkSuccess()
		}
	}
}

// 获取耗时
func getConsTime(ctx *gin.Context) time.Duration {
	// 耗时
	nowTime := ctx.GetTime(REQUESTSTARTTIME)
	consTime := time.Since(nowTime)
	return consTime
}
